package com.fary.context.annotation;

import com.fary.beans.BeanUtils;
import com.fary.beans.factory.config.ConfigurableListableBeanFactory;
import com.fary.beans.factory.support.BeanDefinitionRegistry;
import com.fary.context.ConfigurableApplicationContext;
import com.fary.core.annotation.AnnotationAwareOrderComparator;
import com.fary.context.annotation.ConfigurationCondition.ConfigurationPhase;
import com.fary.core.env.Environment;
import com.fary.core.env.EnvironmentCapable;
import com.fary.core.env.StandardEnvironment;
import com.fary.core.io.DefaultResourceLoader;
import com.fary.core.io.ResourceLoader;
import com.fary.core.type.AnnotatedTypeMetadata;
import com.fary.core.type.AnnotationMetadata;
import com.fary.util.Assert;
import com.fary.util.ClassUtils;
import com.fary.util.MultiValueMap;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class ConditionEvaluator {

    private final ConditionContextImpl context;

    public ConditionEvaluator(BeanDefinitionRegistry registry, Environment environment, ResourceLoader resourceLoader) {
        this.context = new ConditionContextImpl(registry, environment, resourceLoader);
    }

    public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
        return shouldSkip(metadata, null);
    }

    /**
     * 判断是否跳过
     */
    public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
        // 1.没有@Conditional注解，直接返回false
        if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
            return false;
        }

        // 2.如果注册过程为空
        if (phase == null) {
            // 配置类上被注解标注，并且也被@Configuration或@Bean等注解标注
            if (metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
                // 将当前注册阶段标记为转换为配置类阶段继续判定
                return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
            }
            return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
        }

        List<Condition> conditions = new ArrayList<>();
        for (String[] conditionClasses : getConditionClasses(metadata)) {
            for (String conditionClass : conditionClasses) {
                Condition condition = getCondition(conditionClass, this.context.getClassLoader());
                conditions.add(condition);
            }
        }

        AnnotationAwareOrderComparator.sort(conditions);

        // 多个Condition条件，只要有一个返回true，就直接跳过
        for (Condition condition : conditions) {
            ConfigurationPhase requiredPhase = null;
            // condition实现了ConfigurationCondition接口，则先看返回的requiredPhase与注册阶段是否相等，相等的话，则才会判断是否满足条件
            if (condition instanceof ConfigurationCondition) {
                requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
            }
            // condition没有实现了ConfigurationCondition接口，则直接根据match方法判断是否加载配置类
            if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
                return true;
            }
        }
        return false;
    }

    private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
        MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true);
        Object values = (attributes != null ? attributes.get("value") : null);
        return (List<String[]>) (values != null ? values : Collections.emptyList());
    }

    private Condition getCondition(String conditionClassName, ClassLoader classloader) {
        Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName, classloader);
        return (Condition) BeanUtils.instantiateClass(conditionClass);
    }


    private static class ConditionContextImpl implements ConditionContext {

        private final BeanDefinitionRegistry registry;

        private final ConfigurableListableBeanFactory beanFactory;

        private final Environment environment;

        private final ResourceLoader resourceLoader;

        private final ClassLoader classLoader;

        public ConditionContextImpl(BeanDefinitionRegistry registry, Environment environment, ResourceLoader resourceLoader) {

            this.registry = registry;
            this.beanFactory = deduceBeanFactory(registry);
            this.environment = (environment != null ? environment : deduceEnvironment(registry));
            this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry));
            this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);
        }

        private ConfigurableListableBeanFactory deduceBeanFactory(BeanDefinitionRegistry source) {
            if (source instanceof ConfigurableListableBeanFactory) {
                return (ConfigurableListableBeanFactory) source;
            }
            if (source instanceof ConfigurableApplicationContext) {
                return (((ConfigurableApplicationContext) source).getBeanFactory());
            }
            return null;
        }

        private Environment deduceEnvironment(BeanDefinitionRegistry source) {
            if (source instanceof EnvironmentCapable) {
                return ((EnvironmentCapable) source).getEnvironment();
            }
            return new StandardEnvironment();
        }

        private ResourceLoader deduceResourceLoader(BeanDefinitionRegistry source) {
            if (source instanceof ResourceLoader) {
                return (ResourceLoader) source;
            }
            return new DefaultResourceLoader();
        }

        private ClassLoader deduceClassLoader(ResourceLoader resourceLoader, ConfigurableListableBeanFactory beanFactory) {

            if (resourceLoader != null) {
                ClassLoader classLoader = resourceLoader.getClassLoader();
                if (classLoader != null) {
                    return classLoader;
                }
            }
            if (beanFactory != null) {
                return beanFactory.getBeanClassLoader();
            }
            return ClassUtils.getDefaultClassLoader();
        }

        @Override
        public BeanDefinitionRegistry getRegistry() {
            Assert.state(this.registry != null, "No BeanDefinitionRegistry available");
            return this.registry;
        }

        @Override
        public ConfigurableListableBeanFactory getBeanFactory() {
            return this.beanFactory;
        }

        @Override
        public Environment getEnvironment() {
            return this.environment;
        }

        @Override
        public ResourceLoader getResourceLoader() {
            return this.resourceLoader;
        }

        @Override
        public ClassLoader getClassLoader() {
            return this.classLoader;
        }
    }
}